home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Source / mark.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-03-07  |  29.4 KB  |  1,064 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     mark.c
  4.  
  5.     This module handles marking articles read and unread.
  6.     
  7.     Copyright © 1994-1995, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <ctype.h>
  14.  
  15. #include "glob.h"
  16. #include "child.h"
  17. #include "mark.h"
  18. #include "news.h"
  19. #include "wind.h"
  20. #include "subject.h"
  21. #include "memutil.h"
  22. #include "header.h"
  23. #include "dialog.h"
  24. #include "group.h"
  25.  
  26.  
  27.  
  28. /*----------------------------------------------------------------------------
  29.     FindParentCell 
  30.     
  31.     Locate the parent cell in a list window given an index in a group or
  32.     subject array for the window.
  33.             
  34.     Entry:    wind = pointer to group or subject window.
  35.             index = index in group or subject array.
  36.     
  37.     Exit:    function result = true if cell found.
  38.             *theCell = the cell in the window list corresponding to
  39.                 the indexed element of the group or subject array.
  40. ----------------------------------------------------------------------------*/
  41.     
  42. Boolean FindParentCell (WindowPtr wind, short index, Cell *theCell)
  43. {
  44.     TWindow **info;
  45.     ListHandle theList;
  46.     short numCells, i;
  47.     short *pCells;
  48.  
  49.     info = (TWindow**)GetWRefCon(wind);
  50.     theList = (**info).theList;
  51.     numCells = (**theList).dataBounds.bottom;
  52.     pCells = (short*)*((**theList).cells);
  53.     for (i = 0; i < numCells; i++) {
  54.         if (*pCells == index) {
  55.             SetPt(theCell, 0, i);
  56.             return true;
  57.         }
  58.         pCells++;
  59.     }
  60.     return false;
  61. }
  62.  
  63.  
  64.  
  65. /*----------------------------------------------------------------------------
  66.     AppendUnreadRange 
  67.     
  68.     Append a range of unread messages to the end of the unread list for 
  69.     a group.
  70.             
  71.     Entry:    first = number of first unread article in range.
  72.             last = number of last unread article in range.
  73.             *theGroup = group info record for group.
  74.     
  75.     Exit:    function result = error code.
  76. ----------------------------------------------------------------------------*/
  77.  
  78. OSErr AppendUnreadRange (long first, long last, TGroup *theGroup)
  79. {
  80.     TUnread **unread, **lastRec, **nextRec;
  81.     OSErr err = noErr;
  82.  
  83.     lastRec = theGroup->unread;
  84.     if (lastRec != nil) {
  85.         while (true) {
  86.             nextRec = (**lastRec).next;
  87.             if (nextRec == nil) break;
  88.             lastRec = nextRec;
  89.         }
  90.     }
  91.     
  92.     if (lastRec != nil && (**lastRec).lastUnread + 1 == first) {
  93.         (**lastRec).lastUnread = last;
  94.     } else {
  95.         err = MyNewHandleCritical(sizeof(TUnread), &unread);
  96.         if (err != noErr) return err;
  97.         (**unread).firstUnread = first;
  98.         (**unread).lastUnread = last;
  99.         (**unread).next = nil;
  100.         if (lastRec == nil) {
  101.             theGroup->unread = unread;
  102.         } else {
  103.             (**lastRec).next = unread;
  104.         }
  105.     }
  106.     theGroup->numUnread += last - first + 1;
  107.     return noErr;
  108. }
  109.  
  110.  
  111.  
  112. /*----------------------------------------------------------------------------
  113.     MarkOneRead 
  114.     
  115.     Mark a single article in a specified group as being read.
  116.             
  117.     Entry:    theGroup = group record.
  118.             number = article number to mark read.
  119.     
  120.     Exit:    function result = error code.
  121. ----------------------------------------------------------------------------*/
  122.  
  123. static OSErr MarkOneRead (TGroup *theGroup, long number)
  124. {
  125.     TUnread **unread, **newUnread, **prevUnread;
  126.     OSErr err = noErr;
  127.         
  128.     for (unread = prevUnread = theGroup->unread;
  129.         unread != nil && 
  130.             !(number >= (**unread).firstUnread && number <= (**unread).lastUnread);
  131.         prevUnread = unread, unread = (**unread).next) /* do nothing */ ;
  132.     
  133.     if (unread == nil) return noErr; /* already read */
  134.     
  135.     if (number == (**unread).firstUnread || number == (**unread).lastUnread) {
  136.         if (number == (**unread).firstUnread) {
  137.             (**unread).firstUnread++;
  138.         } else {
  139.             (**unread).lastUnread--;
  140.         }
  141.         if ((**unread).firstUnread > (**unread).lastUnread) {
  142.             if (unread != prevUnread) {
  143.                 (**prevUnread).next = (**unread).next;
  144.             } else {
  145.                 theGroup->unread = (**unread).next;
  146.             }
  147.             MyDisposeHandle(unread);
  148.         }
  149.     } else {
  150.         /* split this one in half */
  151.         err = MyNewHandleCritical(sizeof(TUnread), &newUnread);
  152.         if (err != noErr) return err;
  153.         (**newUnread).next = (**unread).next;
  154.         (**unread).next = newUnread;
  155.         (**newUnread).lastUnread = (**unread).lastUnread;
  156.         (**newUnread).firstUnread = number + 1;
  157.         (**unread).lastUnread = number - 1;
  158.     }
  159.     theGroup->numUnread -= 1;
  160.     return noErr;
  161. }
  162.  
  163.  
  164.  
  165. /*----------------------------------------------------------------------------
  166.     MarkOneUnread 
  167.     
  168.     Mark a single article in a specified group as being unread.
  169.             
  170.     Entry:    theGroup = group record.
  171.             number = article number to mark unread.
  172.     
  173.     Exit:    function result = error code.
  174. ----------------------------------------------------------------------------*/
  175.  
  176. static OSErr MarkOneUnread (TGroup *theGroup, long number)
  177. {
  178.     TUnread **unread, **newUnread, **prevUnread;
  179.     OSErr err = noErr;
  180.         
  181.     for (unread = prevUnread = theGroup->unread;
  182.         unread != nil && number > (**unread).lastUnread;
  183.         prevUnread = unread, unread = (**unread).next) /* do nothing */ ;
  184.     
  185.     if (unread != nil && number >= (**unread).firstUnread) return noErr; /* already unread */
  186.     
  187.     if (unread == nil) {
  188.         /* insert at end of list, after prevUnread. */
  189.         if (prevUnread != nil && (**prevUnread).lastUnread == number-1) {
  190.             (**prevUnread).lastUnread++;
  191.         } else {
  192.             err = MyNewHandleCritical(sizeof(TUnread), &newUnread);
  193.             if (err != noErr) return err;
  194.             (**newUnread).firstUnread = (**newUnread).lastUnread = number;
  195.             (**newUnread).next = nil;
  196.             if (prevUnread == nil) {
  197.                 theGroup->unread = newUnread;
  198.             } else {
  199.                 (**prevUnread).next = newUnread;
  200.             }
  201.         }
  202.     } else if (unread == prevUnread) {
  203.         /* insert at beginning of list, before unread. */
  204.         if ((**unread).firstUnread == number+1) {
  205.             (**unread).firstUnread--;
  206.         } else {
  207.             err = MyNewHandleCritical(sizeof(TUnread), &newUnread);
  208.             if (err != noErr) return err;
  209.             (**newUnread).firstUnread = (**newUnread).lastUnread = number;
  210.             (**newUnread).next = unread;
  211.             theGroup->unread = newUnread;
  212.         }
  213.     } else {
  214.         /* insert in middle of list, between prevUnread and unread. */
  215.         if ((**prevUnread).lastUnread == number-1 || 
  216.             (**unread).firstUnread == number+1) 
  217.         {
  218.             if ((**prevUnread).lastUnread == number-1) {
  219.                 (**prevUnread).lastUnread++;
  220.             } else {
  221.                 (**unread).firstUnread--;
  222.             }
  223.             if ((**prevUnread).lastUnread >= (**unread).firstUnread - 1) {
  224.                 /* join two blocks together */
  225.                 (**prevUnread).lastUnread = (**unread).lastUnread;
  226.                 (**prevUnread).next = (**unread).next;
  227.                 MyDisposeHandle(unread);
  228.             }
  229.         } else {
  230.             err = MyNewHandleCritical(sizeof(TUnread), &newUnread);
  231.             if (err != noErr) return err;
  232.             (**newUnread).firstUnread = (**newUnread).lastUnread = number;
  233.             (**prevUnread).next = newUnread;
  234.             (**newUnread).next = unread;
  235.         }
  236.     }
  237.     theGroup->numUnread += 1;
  238.     return noErr;
  239. }
  240.  
  241.  
  242.  
  243. /*----------------------------------------------------------------------------
  244.     AdjustUnreadList 
  245.     
  246.     Trim the unread list for a group to include just articles in the range
  247.     [firstMess, lastMess].
  248.             
  249.     Entry:    theGroup = pointer to group record.
  250. ----------------------------------------------------------------------------*/
  251.  
  252. void AdjustUnreadList (TGroup *theGroup)
  253. {
  254.     TUnread **prev, **cur;
  255.     
  256.     /* Delete all unread ranges which come before firstMess. */
  257.     
  258.     cur = theGroup->unread;
  259.     while (cur && (**cur).lastUnread < theGroup->firstMess) {
  260.         prev = cur;
  261.         cur = (**cur).next;
  262.         MyDisposeHandle(prev);
  263.     }
  264.     theGroup->unread = cur;
  265.     
  266.     /* If necessary, trim the first unread range to begin at firstMess. */
  267.     
  268.     if (cur && (**cur).firstUnread < theGroup->firstMess) 
  269.         (**cur).firstUnread = theGroup->firstMess;
  270.     
  271.     /* Leave alone all unread ranges which come before lastMess. */    
  272.     
  273.     prev = nil;
  274.     while (cur && (**cur).firstUnread <= theGroup->lastMess) {
  275.         prev = cur;
  276.         cur = (**cur).next; 
  277.     }
  278.     
  279.     /* If necessary, trim the last unread range to end at lastMess. */
  280.     
  281.     if (prev && (**prev).lastUnread > theGroup->lastMess)
  282.         (**prev).lastUnread = theGroup->lastMess;
  283.     if (prev) {
  284.         (**prev).next = nil;
  285.     } else {
  286.         theGroup->unread = nil;
  287.     }
  288.         
  289.     /* Delete the remaining unread ranges, which all come after lastMess. */
  290.         
  291.     while (cur) {
  292.         prev = cur;
  293.         cur = (**cur).next;
  294.         MyDisposeHandle(prev);
  295.     }
  296.     
  297.     /* Recalculate numUnread. */
  298.     
  299.     theGroup->numUnread = 0;
  300.     cur = theGroup->unread;
  301.     while (cur) {
  302.         theGroup->numUnread += (**cur).lastUnread - (**cur).firstUnread + 1;
  303.         cur = (**cur).next;
  304.     }
  305. }
  306.  
  307.  
  308.  
  309. /*----------------------------------------------------------------------------
  310.     UpdateUnreadList 
  311.     
  312.     Update the unread list for a group in a user group list window to 
  313.     match the read/unread article marks in the corresponding open 
  314.     subject window.
  315.             
  316.     Entry:    wind = pointer to subject window.
  317.     
  318.     Exit:    function result = error code.
  319. ----------------------------------------------------------------------------*/
  320.  
  321. OSErr UpdateUnreadList (WindowPtr wind)
  322. {
  323.     long tmpFirst,tmpLast,i;
  324.     TGroup theGroup,**groupArray;
  325.     TSubject **subjectArray;
  326.     TWindow **info,**parentInfo;
  327.     short index, numSubjectsInList;
  328.     WindowPtr parentWindow;
  329.     long firstFetched;
  330.     TUnread **unread, **prevUnread=nil, **nextUnread;
  331.     long **sortByNumber;
  332.     TSubject *p, *q;
  333.     long *x;
  334.     OSErr err = noErr;
  335.     
  336.     info = (TWindow**)GetWRefCon(wind);
  337.     index = (**info).parentGroup;
  338.     subjectArray = (**info).subjectArray;
  339.     sortByNumber = (**info).sortByNumber;
  340.     numSubjectsInList = (**info).numSubjectsInList;
  341.     parentWindow = (**info).parentWindow;
  342.     parentInfo = (TWindow**)GetWRefCon(parentWindow);
  343.     if ((**parentInfo).groupKind != kUserGroup) return noErr;
  344.     groupArray = (**parentInfo).groupArray;
  345.     theGroup = (*groupArray)[index];
  346.     
  347.     /* Truncate the unread list for the group so that the unread list
  348.        includes only articles which were not fetched from the server
  349.        (articles with numbers less than firstFetched). */
  350.     
  351.     firstFetched = (**info).firstFetched;
  352.     unread = theGroup.unread;
  353.     theGroup.numUnread = 0;
  354.     while (unread != nil) {
  355.         if (firstFetched <= (**unread).lastUnread) break;
  356.         theGroup.numUnread += (**unread).lastUnread - (**unread).firstUnread + 1;
  357.         prevUnread = unread;
  358.         unread = (**unread).next;
  359.     }
  360.     if (unread != nil) {
  361.         if ((**unread).firstUnread < firstFetched) {
  362.             (**unread).lastUnread = firstFetched-1;
  363.             theGroup.numUnread += firstFetched - (**unread).firstUnread;
  364.             prevUnread = unread;
  365.             unread = (**unread).next;
  366.             (**prevUnread).next = nil;
  367.         } else {
  368.             if (prevUnread == nil) {
  369.                 theGroup.unread = nil;
  370.             } else {
  371.                 (**prevUnread).next = nil;
  372.             }
  373.         }
  374.         while (unread != nil) {
  375.             nextUnread = (**unread).next;
  376.             MyDisposeHandle(unread);
  377.             unread = nextUnread;
  378.         }
  379.     }
  380.     
  381.     (**parentInfo).changed = true;
  382.     
  383.     /* Walk the article list for the subject window in increasing order
  384.        by article number. While the article numbers are less that firstFetched,
  385.        individually mark them read or unread in the group's unread list. 
  386.        Note that any such articles came from the article cache, not from
  387.        the server. */
  388.        
  389.     for (i = 0; i < numSubjectsInList; i++) {
  390.         x = *sortByNumber + i;
  391.         p = (TSubject*)((char*)*subjectArray + *x);
  392.         if (p->number >= firstFetched) break;
  393.         if (p->read) {
  394.             err = MarkOneRead(&theGroup, p->number);
  395.         } else {
  396.             err = MarkOneUnread(&theGroup, p->number);
  397.         }
  398.         if (err != noErr) return err;
  399.     }
  400.     
  401.     /* Walk the rest of the article list for the subject window in increasing 
  402.        order by article number and append new unread article ranges to the end
  403.        of the unread list for the group. */
  404.     
  405.     tmpFirst = -1;
  406.     
  407.     for (; i < numSubjectsInList; i++) {
  408.         x = *sortByNumber + i;
  409.         p = (TSubject*)((char*)*subjectArray + *x);
  410.         q = (TSubject*)((char*)*subjectArray + *(x+1));
  411.         if (!p->read && i < numSubjectsInList-1 && q->number - p->number > 1) {
  412.             if (tmpFirst == -1) tmpFirst = p->number;
  413.             err = AppendUnreadRange(tmpFirst, p->number, &theGroup);
  414.             if (err != noErr) return err;
  415.             tmpFirst = -1;
  416.         } else if (tmpFirst == -1 && !p->read) {
  417.             tmpFirst = tmpLast = p->number;
  418.         } else if (tmpFirst != -1 && !p->read) {
  419.             tmpLast = p->number;
  420.         } else if (tmpFirst != -1 && p->read) {
  421.             err = AppendUnreadRange(tmpFirst, tmpLast, &theGroup);
  422.             if (err != noErr) return err;
  423.             tmpFirst = -1;
  424.         }
  425.     }
  426.     if (tmpFirst != -1) {
  427.         err = AppendUnreadRange(tmpFirst, tmpLast, &theGroup);
  428.         if (err != noErr) return err;
  429.     }
  430.         
  431.     (*groupArray)[index] = theGroup;
  432.     return noErr;
  433. }
  434.  
  435.  
  436.  
  437. /*----------------------------------------------------------------------------
  438.     AdjustGroupUnreadCount 
  439.     
  440.     Adjust the number of unread articles for a group in a parent group list 
  441.     window when one or more subjects in a subject list window have been
  442.     marked read or unread.
  443.             
  444.     Entry:    wind = pointer to subject window.
  445.             deltaUnread = change in number of unread articles for group.
  446. ----------------------------------------------------------------------------*/
  447.     
  448. static void AdjustGroupUnreadCount (WindowPtr wind, short deltaUnread)
  449. {
  450.     WindowPtr parent;
  451.     TWindow **info, **parentInfo;
  452.     TGroup **parentGroupArray;
  453.     ListHandle parentList;
  454.     long numUnread;
  455.     short index;
  456.     Cell theCell;
  457.     
  458.     if (deltaUnread == 0) return;
  459.     info = (TWindow**)GetWRefCon(wind);
  460.     parent = (**info).parentWindow;
  461.     parentInfo = (TWindow**)GetWRefCon(parent);
  462.     if ((**parentInfo).groupKind != kUserGroup) return;
  463.     parentGroupArray = (**parentInfo).groupArray;
  464.     parentList = (**parentInfo).theList;
  465.     index = (**info).parentGroup;
  466.     if (FindParentCell(parent, index, &theCell)) {
  467.         numUnread = (*parentGroupArray)[index].numUnread;
  468.         numUnread += deltaUnread;
  469.         if (numUnread < 0) numUnread = 0;
  470.         (*parentGroupArray)[index].numUnread = numUnread;
  471.         (*parentGroupArray)[index].onlyRedrawCount = true;
  472.         LDraw(theCell, parentList);
  473.         (*parentGroupArray)[index].onlyRedrawCount = false;
  474.     }
  475. }
  476.  
  477.  
  478.  
  479. /*----------------------------------------------------------------------------
  480.     AdjustArticleWindowTitle 
  481.     
  482.     Mark an article window read or unread.
  483.             
  484.     Entry:    wind = pointer to article window.
  485.             read = true to mark read, false to mark unread.
  486. ----------------------------------------------------------------------------*/
  487.  
  488. static void AdjustArticleWindowTitle (WindowPtr wind, Boolean read)
  489. {
  490.     Str255 title;
  491.  
  492.     GetWTitle(wind,title);
  493.     if (read) {
  494.         if (title[1] == checkMark) return;
  495.         if (*title == 255) *title = 254;
  496.         BlockMoveData(title+1, title+2, *title);
  497.         title[1] = checkMark;
  498.         (*title)++;
  499.     } else {
  500.         if (title[1] != checkMark) return;
  501.         (*title)--;
  502.         BlockMoveData(title+2, title+1, *title);
  503.     }
  504.     SetWTitle(wind,title);
  505. }
  506.  
  507.  
  508.  
  509. /*----------------------------------------------------------------------------
  510.     MarkSubjectCell 
  511.     
  512.     Mark a single cell in a subject window read or unread. Any open child window 
  513.     of the cell is also marked. If the cell is collapsed, all the articles in the 
  514.     thread are marked, as are any open children.
  515.             
  516.     Entry:    wind = pointer to subject window.
  517.             theCell = cell in subject window to be marked.
  518.             read = true to mark read, false to mark unread.
  519.                 
  520.     Exit:    *numMarked = number of articles marked.
  521. ----------------------------------------------------------------------------*/
  522.  
  523. static void MarkSubjectCell (WindowPtr wind, Cell theCell, Boolean read, short *numMarked)
  524. {
  525.     WindowPtr child;
  526.     TWindow **info;
  527.     ListHandle theList;
  528.     TSubject **subjectArray;
  529.     short index, cellDataLen, nextInThread;
  530.  
  531.     *numMarked = 0;
  532.     info = (TWindow**)GetWRefCon(wind);
  533.     theList = (**info).theList;
  534.     subjectArray = (**info).subjectArray;
  535.     cellDataLen = 2;
  536.     LGetCell(&index, &cellDataLen, theCell, theList);
  537.     if ((*subjectArray)[index].read != read) {
  538.         (*subjectArray)[index].read = read;
  539.         RedrawSubjectWindowUnreadCount(wind);
  540.         (*numMarked)++;
  541.     }
  542.     child = FindChildByIndex(wind, index);
  543.     if (child != nil) AdjustArticleWindowTitle(child, read);
  544.     if ((*subjectArray)[index].collapsed) {
  545.         nextInThread = (*subjectArray)[index].nextInThread;
  546.         while (nextInThread != -1) {
  547.             if ((*subjectArray)[nextInThread].read != read) {
  548.                 (*subjectArray)[nextInThread].read = read;
  549.                 RedrawSubjectWindowUnreadCount(wind);
  550.                 (*numMarked)++;
  551.             }
  552.             child = FindChildByIndex(wind, nextInThread);
  553.             if (child != nil) AdjustArticleWindowTitle(child, read);
  554.             nextInThread = (*subjectArray)[nextInThread].nextInThread;
  555.         }
  556.     }
  557.     (*subjectArray)[index].onlyRedrawCheck = true;
  558.     LDraw(theCell, theList);
  559.     (*subjectArray)[index].onlyRedrawCheck = false;
  560. }
  561.  
  562.  
  563.  
  564. /*----------------------------------------------------------------------------
  565.     MarkSubjectCellRead 
  566.     
  567.     Mark a single subject cell read.
  568.     
  569.     Mark a single cell in a subject window read. Any open child window 
  570.     of the cell is also marked. If the cell is collapsed, all the articles in the 
  571.     thread are marked, as are any open children. The unread count for the group
  572.     is adjusted.
  573.             
  574.     Entry:    wind = pointer to subject window.
  575.             theCell = cell in subject window to be marked read.
  576. ----------------------------------------------------------------------------*/
  577.  
  578. void MarkSubjectCellRead (WindowPtr wind, Cell theCell)
  579. {
  580.     short numMarked;
  581.  
  582.     MarkSubjectCell(wind, theCell, true, &numMarked);
  583.     AdjustGroupUnreadCount(wind, -numMarked);
  584. }
  585.     
  586.  
  587.  
  588.  
  589. /*----------------------------------------------------------------------------
  590.     MarkOneXref 
  591.     
  592.     Mark a single cross-referenced message in a specified group as
  593.     being read or unread.
  594.             
  595.     Entry:    groupName = name of group.
  596.             number = article number to mark read.
  597.             wind = pointer to group window.
  598.             read = true to mark read, false to mark unread.
  599.     
  600.     Exit:    function result = error code.
  601. ----------------------------------------------------------------------------*/
  602.  
  603. static OSErr MarkOneXref (char *groupName, long number, WindowPtr wind, 
  604.     Boolean read)
  605. {
  606.     TWindow **info, **childInfo;
  607.     TGroup theGroup, **groupArray;
  608.     ListHandle theList, childList;
  609.     TSubject **subjectArray, *pSubject;
  610.     Cell theCell, childCell;
  611.     short index, childIndex, cellDataLen, numCells, numSubjects;
  612.     WindowPtr child, grandChild;
  613.     OSErr err = noErr;
  614.     
  615.     info = (TWindow**)GetWRefCon(wind);
  616.     theList = (**info).theList;
  617.     groupArray = (**info).groupArray;
  618.     numCells = (**theList).dataBounds.bottom;
  619.     theCell.h = 0;
  620.     for (theCell.v = 0; theCell.v < numCells; theCell.v++) {
  621.         cellDataLen = 2;
  622.         LGetCell(&index, &cellDataLen, theCell, theList);
  623.         if (strcmp(*gGroupNames + (*groupArray)[index].nameOffset, 
  624.             groupName) == 0) break;
  625.     }
  626.     if (theCell.v >= numCells) return noErr;
  627.     child = FindChild(wind, theCell);
  628.     if (child != nil) {
  629.         childInfo = (TWindow**)GetWRefCon(child);
  630.         childList = (**childInfo).theList;
  631.         subjectArray = (**childInfo).subjectArray;
  632.         numSubjects = (**childInfo).numSubjects;
  633.         pSubject = *subjectArray;
  634.         for (childIndex = 0; childIndex < numSubjects; childIndex++) {
  635.             if (pSubject->number == number && pSubject->inList) break;
  636.             pSubject++;
  637.         }
  638.         if (childIndex >= numSubjects) return noErr;
  639.         if ((*subjectArray)[childIndex].read != read) {
  640.             (*subjectArray)[childIndex].read = read;
  641.             RedrawSubjectWindowUnreadCount(child);
  642.             if ((*subjectArray)[childIndex].collapsed) 
  643.                 childIndex = (*subjectArray)[childIndex].threadHeadIndex;
  644.             if (!FindParentCell(child, childIndex, &childCell)) return noErr;
  645.             (*subjectArray)[childIndex].onlyRedrawCheck = true;
  646.             LDraw(childCell, childList);
  647.             (*subjectArray)[childIndex].onlyRedrawCheck = false;
  648.             (*groupArray)[index].numUnread += read ? -1 : 1;
  649.             (*groupArray)[index].onlyRedrawCount = true;
  650.             LDraw(theCell, theList);
  651.             (*groupArray)[index].onlyRedrawCount = false;
  652.             grandChild = FindChildByIndex(child, childIndex);
  653.             if (grandChild != nil) AdjustArticleWindowTitle(grandChild, read);
  654.         }
  655.     } else {
  656.         theGroup = (*groupArray)[index];
  657.         if (read) {
  658.             err = MarkOneRead(&theGroup, number);
  659.         } else {
  660.             err = MarkOneUnread(&theGroup, number);
  661.         }
  662.         if (err != noErr) return err;
  663.         (*groupArray)[index] = theGroup;
  664.         (*groupArray)[index].onlyRedrawCount = true;
  665.         LDraw(theCell, theList);
  666.         (*groupArray)[index].onlyRedrawCount = false;
  667.     }
  668.     return noErr;
  669. }
  670.  
  671.  
  672.  
  673. /*----------------------------------------------------------------------------
  674.     MarkXrefs 
  675.     
  676.     Mark articles which appear in multiple newsgroups as read or unread in
  677.     all of the groups.
  678.             
  679.     Entry:    wind = pointer to article window.
  680.             read = true to mark read, false to mark unread
  681.             
  682.     Exit:    function result = error code.
  683. ----------------------------------------------------------------------------*/
  684.  
  685. static OSErr MarkXrefs (WindowPtr wind, Boolean read)
  686. {
  687.     TWindow **info, **parentInfo, **grandParentInfo;
  688.     WindowPtr parent, grandParent;
  689.     Handle xrefs;
  690.     CStr255 groupName;
  691.     char *p, *pEnd, *groupNameStart;
  692.     long offset, len, number, groupNameLen;
  693.     OSErr err = noErr;
  694.     
  695.     info = (TWindow**)GetWRefCon(wind);
  696.     if ((**info).kind != kArticle) return noErr;
  697.     parent = (**info).parentWindow;
  698.     if (parent == nil) return noErr;
  699.     parentInfo = (TWindow**)GetWRefCon(parent);
  700.     grandParent = (**parentInfo).parentWindow;
  701.     grandParentInfo = (TWindow**)GetWRefCon(grandParent);
  702.     if ((**grandParentInfo).groupKind != kUserGroup) return noErr;
  703.         
  704.     err = FindHeaderHandle((**info).fullText, "Xref", &xrefs);
  705.     if (err != noErr) goto exit;
  706.     if (xrefs == nil) return noErr;
  707.     len = MyGetHandleSize(xrefs);
  708.     
  709.     /* Skip over site name */
  710.     
  711.     p = *xrefs;
  712.     pEnd = p + MyGetHandleSize(xrefs);
  713.     while (p < pEnd && *p != ' ') p++;
  714.     
  715.     /* parse xrefed group:number pairs */
  716.     
  717.     while (p < pEnd) {
  718.         while (p < pEnd && isLWSP(*p)) p++;
  719.         groupNameStart = p;
  720.         while (p < pEnd && *p != ':') p++;
  721.         if (p >= pEnd) break;
  722.         groupNameLen = p - groupNameStart;
  723.         if (groupNameLen > 255) break;
  724.         if (groupNameLen == 0) break;
  725.         BlockMoveData(groupNameStart, groupName, groupNameLen);
  726.         groupName[groupNameLen] = 0;
  727.         p++;
  728.         if (p >= pEnd) break;
  729.         if (!isdigit(*p)) break;
  730.         number = *p - '0';
  731.         p++;
  732.         while (p < pEnd && isdigit(*p)) {
  733.             number = 10*number + (*p - '0');
  734.             p++;
  735.         }
  736.         offset = p - *xrefs;
  737.         err = MarkOneXref(groupName, number, grandParent, read);
  738.         if (err != noErr) goto exit;
  739.         p = *xrefs + offset;
  740.         pEnd = *xrefs + len;
  741.     }
  742.     
  743.     MyDisposeHandle(xrefs);
  744.     return noErr;
  745.  
  746. exit:
  747.  
  748.     MyDisposeHandle(xrefs);
  749.     return err;
  750. }
  751.  
  752.  
  753.  
  754. /*----------------------------------------------------------------------------
  755.     MarkSelectedSubjects 
  756.     
  757.     Mark all selected subjects in a subject window read or unread.
  758.             
  759.     Entry:    wind = pointer to subject window.
  760.             read = true to mark read, false to mark unread.
  761. ----------------------------------------------------------------------------*/
  762.  
  763. static void MarkSelectedSubjects (WindowPtr wind, Boolean read)
  764. {
  765.     TWindow **info;
  766.     Cell theCell;
  767.     short totMarked = 0, numMarked;
  768.     
  769.     info = (TWindow**)GetWRefCon(wind);
  770.     if ((**info).kind != kSubject) return;
  771.  
  772.     SetPt(&theCell, 0, 0);
  773.     while (LGetSelect(true, &theCell, (**info).theList)) {
  774.         MarkSubjectCell(wind, theCell, read, &numMarked);
  775.         theCell.v++;
  776.         totMarked += numMarked;
  777.     }
  778.     AdjustGroupUnreadCount(wind, read ? -totMarked : totMarked);
  779. }
  780.  
  781.  
  782.  
  783. /*----------------------------------------------------------------------------
  784.     MarkAllSubjects 
  785.     
  786.     Mark all subjects in a subject window read or unread.
  787.             
  788.     Entry:    wind = pointer to subject window.
  789.             read = true to mark read, false to mark unread.
  790. ----------------------------------------------------------------------------*/
  791.  
  792. void MarkAllSubjects (WindowPtr wind, Boolean read)
  793. {
  794.     TWindow **info;
  795.     Cell theCell;
  796.     ListHandle theList;
  797.     short numCells;
  798.     short totMarked = 0, numMarked;
  799.     
  800.     info = (TWindow**)GetWRefCon(wind);
  801.     theList = (**info).theList;
  802.     numCells = (**theList).dataBounds.bottom;
  803.     theCell.h = 0;
  804.     for (theCell.v = 0; theCell.v < numCells; theCell.v++) {
  805.         MarkSubjectCell(wind, theCell, read, &numMarked);
  806.         totMarked += numMarked;
  807.     }
  808.     AdjustGroupUnreadCount(wind, read ? -totMarked : totMarked);
  809. }
  810.  
  811.  
  812.  
  813. /*----------------------------------------------------------------------------
  814.     MarkArticle 
  815.     
  816.     Mark an article window read or unread.
  817.             
  818.     Entry:    wind = pointer to article window.
  819.             read = true to mark read, false to mark unread.
  820.             
  821.     Exit:    function result = error code.
  822. ----------------------------------------------------------------------------*/
  823.  
  824. OSErr MarkArticle (WindowPtr wind, Boolean read)
  825. {
  826.     WindowPtr parent;
  827.     TWindow **info,**parentInfo;
  828.     Cell theCell;
  829.     ListHandle parentList;
  830.     TSubject **subjectArray;
  831.     short index;
  832.     
  833.     info = (TWindow**)GetWRefCon(wind);
  834.     index = (**info).parentSubject;
  835.     parent = (**info).parentWindow;
  836.     if (parent == nil) return noErr;
  837.     parentInfo = (TWindow**)GetWRefCon(parent);
  838.     subjectArray = (**parentInfo).subjectArray;
  839.     parentList = (**parentInfo).theList;
  840.         
  841.     AdjustArticleWindowTitle(wind, read);
  842.     
  843.     if ((*subjectArray)[index].read != read) {
  844.         (*subjectArray)[index].read = read;
  845.         RedrawSubjectWindowUnreadCount(parent);
  846.         if ((*subjectArray)[index].collapsed) index = (*subjectArray)[index].threadHeadIndex;
  847.         if (FindParentCell(parent, index, &theCell)) {
  848.             (*subjectArray)[index].onlyRedrawCheck = true;
  849.             LDraw(theCell, parentList);
  850.             (*subjectArray)[index].onlyRedrawCheck = false;
  851.         }
  852.         AdjustGroupUnreadCount(parent, read ? -1 : +1);
  853.     }
  854.     
  855.     return MarkXrefs(wind, read);
  856. }
  857.  
  858.  
  859.  
  860. /*----------------------------------------------------------------------------
  861.     MarkSelectedGroups 
  862.     
  863.     Mark all selected groups in a group window read or unread.
  864.             
  865.     Entry:    wind = pointer to user group window.
  866.             read = true to mark read, false to mark unread.
  867.             maxUnread = maximum number of articles to mark unread
  868.                 if read = false. 
  869.             
  870.     Exit:    function result = error code.
  871. ----------------------------------------------------------------------------*/
  872.  
  873. OSErr MarkSelectedGroups (WindowPtr wind, Boolean read, long maxUnread)
  874. {
  875.     WindowPtr child;
  876.     TWindow **info;
  877.     Cell theCell;
  878.     short index, cellDataLen;
  879.     TGroup **groupArray, theGroup;
  880.     ListHandle theList;
  881.     TUnread **pUnreadRec;
  882.     OSErr err = noErr;
  883.     long firstUnread, lastUnread;
  884.     
  885.     info = (TWindow**)GetWRefCon(wind);
  886.     if ((**info).groupKind != kUserGroup) return noErr;
  887.     theList = (**info).theList;
  888.     groupArray = (**info).groupArray;
  889.         
  890.     SetPt(&theCell,0,0);
  891.     while (LGetSelect(true, &theCell, theList)) {
  892.     
  893.         cellDataLen = 2;
  894.         LGetCell(&index, &cellDataLen, theCell, theList);
  895.         
  896.         theGroup = (*groupArray)[index];
  897.         
  898.         DisposeGroupUnreadList(&theGroup);
  899.         
  900.         if (!read) {
  901.             err = MyNewHandle(sizeof(TUnread), &pUnreadRec);
  902.             if (err != noErr) return err;
  903.             firstUnread = theGroup.firstMess;
  904.             lastUnread = theGroup.lastMess;
  905.             if (lastUnread - maxUnread + 1 > firstUnread) 
  906.                 firstUnread = lastUnread - maxUnread + 1;
  907.             (**pUnreadRec).firstUnread = firstUnread;
  908.             (**pUnreadRec).lastUnread = lastUnread;
  909.             (**pUnreadRec).next = nil;
  910.             theGroup.unread = pUnreadRec;
  911.             theGroup.numUnread = lastUnread - firstUnread + 1;
  912.         }
  913.         
  914.         (*groupArray)[index] = theGroup;
  915.         
  916.         (*groupArray)[index].onlyRedrawCount = true;
  917.         LDraw(theCell, theList);
  918.         (*groupArray)[index].onlyRedrawCount = false;
  919.  
  920.         child = FindChild(wind, theCell);
  921.         if (child != nil) MarkAllSubjects(child, read);
  922.         
  923.         (**info).changed = true;
  924.         
  925.         theCell.v++;
  926.     }
  927.     
  928.     return noErr;
  929. }
  930.  
  931.  
  932.  
  933. /*----------------------------------------------------------------------------
  934.     MarkThread 
  935.     
  936.     Mark all the articles in a thread read or unread.
  937.             
  938.     Entry:    wind = pointer to subjet window.
  939.             threadHeadIndex = index in subject array of first article
  940.                 in thread.
  941.             read = true to mark read, false to mark unread.
  942. ----------------------------------------------------------------------------*/
  943.  
  944. void MarkThread (WindowPtr wind, short threadHeadIndex, Boolean read)
  945. {
  946.     TWindow **info;
  947.     TSubject **subjectArray;
  948.     WindowPtr child;
  949.     short index, numMarked = 0;
  950.     Boolean collapsed;
  951.     Cell theCell;
  952.     ListHandle theList;
  953.     
  954.     info = (TWindow**)GetWRefCon(wind);
  955.     theList = (**info).theList;
  956.     subjectArray = (**info).subjectArray;
  957.     collapsed = (*subjectArray)[threadHeadIndex].collapsed;
  958.     if (!FindParentCell(wind, threadHeadIndex, &theCell)) return;
  959.     index = threadHeadIndex;
  960.     while (index != -1) {
  961.         if ((*subjectArray)[index].read != read) {
  962.             (*subjectArray)[index].read = read;
  963.             numMarked++;
  964.             child = FindChildByIndex(wind, index);
  965.             if (child != nil) AdjustArticleWindowTitle(child, read);
  966.             if (!collapsed) {
  967.                 (*subjectArray)[index].onlyRedrawCheck = true;
  968.                 LDraw(theCell, theList);
  969.                 (*subjectArray)[index].onlyRedrawCheck = false;
  970.             }
  971.         }
  972.         if (!collapsed) theCell.v++;
  973.         index = (*subjectArray)[index].nextInThread;
  974.     }
  975.     if (collapsed) {
  976.         (*subjectArray)[threadHeadIndex].onlyRedrawCheck = true;
  977.         LDraw(theCell, theList);
  978.         (*subjectArray)[threadHeadIndex].onlyRedrawCheck = false;
  979.     }
  980.  
  981.     AdjustGroupUnreadCount(wind, read ? -numMarked : numMarked);
  982. }
  983.  
  984.  
  985.  
  986. /*----------------------------------------------------------------------------
  987.     DoMarkCommand 
  988.     
  989.     Handle the "Mark Read" and "Mark Unread" commands.
  990.             
  991.     Entry:    wind = pointer to group, subject, or article window.
  992.             read = true to mark read, false to mark unread.
  993.             
  994.     Exit:    function result = error code.
  995. ----------------------------------------------------------------------------*/
  996.  
  997. OSErr DoMarkCommand (WindowPtr wind, Boolean read)
  998. {
  999.     TWindow **info;
  1000.     OSErr err = noErr;
  1001.     
  1002.     info = (TWindow**)GetWRefCon(wind);
  1003.  
  1004.     switch ((**info).kind) {
  1005.         case kGroup:
  1006.             if ((**info).groupKind == kUserGroup) {
  1007.                 err = MarkSelectedGroups(wind, read, 0x70000000);
  1008.                 if (err != noErr) return err;
  1009.             }
  1010.             break;
  1011.         case kSubject:
  1012.             MarkSelectedSubjects(wind, read);
  1013.             break;
  1014.         case kArticle:
  1015.             err = MarkArticle(wind, read);
  1016.             if (err != noErr) return err;
  1017.             break;
  1018.     }
  1019.     return noErr;
  1020. }
  1021.  
  1022.  
  1023.  
  1024. /*----------------------------------------------------------------------------
  1025.     DoMarkOthersRead 
  1026.     
  1027.     Handle the "Mark Others Read" command.
  1028.             
  1029.     Entry:    wind = pointer to subject window.
  1030.             
  1031.     Exit:    function result = error code.
  1032. ----------------------------------------------------------------------------*/
  1033.  
  1034. OSErr DoMarkOthersRead (WindowPtr wind)
  1035. {
  1036.     TWindow **info;
  1037.     ListHandle theList;
  1038.     Cell theCell;
  1039.     short totMarked = 0, numMarked;
  1040.     short *p, *pEnd;
  1041.     char state;
  1042.     
  1043.     info = (TWindow**)GetWRefCon(wind);
  1044.     if ((**info).kind != kSubject) return noErr;
  1045.     theList = (**info).theList;
  1046.  
  1047.     SetPt(&theCell, 0, 0);
  1048.     state = MyHGetState(theList);
  1049.     MyHLock(theList);
  1050.     p = (**theList).cellArray;
  1051.     pEnd = p + (**theList).dataBounds.bottom;
  1052.     while (p < pEnd) {
  1053.         if ((*p & 0x8000) == 0) {
  1054.             MarkSubjectCell(wind, theCell, true, &numMarked);
  1055.             totMarked += numMarked;
  1056.         }
  1057.         p++;
  1058.         theCell.v++;
  1059.     }
  1060.     MyHSetState(theList, state);
  1061.     AdjustGroupUnreadCount(wind, -totMarked);
  1062.     return noErr;
  1063. }
  1064.